﻿using RockBus.BufferManager.Interfaces;
using RockBus.Logging.Interfaces;
using System;
using System.IO;
using System.Reflection;

namespace RockBus.Transport.Db
{
    public class WritableChunkingStream : Stream
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="chunkSize"></param>
        /// <param name="processChunkAction">The action, that is called for every chunk. Parameters: buffer, offset, count, index</param>
        public WritableChunkingStream(ILogger logger, IBufferManager bufferManager, int chunkSize, Action<byte[], int, int, int> processChunkAction)
            : base()
        {
            this.Logger = logger;
            this.BufferManager = bufferManager;
            this.ChunkSize = chunkSize;
            this.ChunkIndex = 0;
            this.ProcessChunkAction = processChunkAction;
            this.ChunkBuffer = this.BufferManager.TakeBuffer(this.ChunkSize);
        }

        public ILogger Logger { get; set; }

        private IBufferManager BufferManager { get; set; }

        public int ChunkSize { get; set; }

        private int ChunkIndex { get; set; }

        private byte[] ChunkBuffer { get; set; }

        private int ChunkOffset { get; set; }

        private int ChunkRemainder { get { return this.ChunkSize - this.ChunkOffset; } }

        private Action<byte[], int, int, int> ProcessChunkAction { get; set; }

        public override bool CanRead { get { return false; } }

        public override bool CanSeek { get { return false; } }

        public override bool CanWrite { get { return true; } }

        public override void Flush()
        {
            // Intentionally left blank
        }

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        private long _position = 0;

        public override long Position
        {
            get { return this._position; }
            set { throw new NotImplementedException(); }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "WCS: stage 0: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

            while ((count > 0) && (count >= this.ChunkRemainder))
            {
                this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "WCS: stage 1: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

                Buffer.BlockCopy(buffer, offset, this.ChunkBuffer, this.ChunkOffset, this.ChunkRemainder);
                this._position += this.ChunkRemainder;

                offset += this.ChunkRemainder;
                count -= this.ChunkRemainder;

                this.ChunkOffset = 0;
                ProcessChunkAction(this.ChunkBuffer, 0, this.ChunkSize, this.ChunkIndex);
                this.ChunkIndex++;
            }

            if (count > 0)
            {
                this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "WCS: stage 2: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

                Buffer.BlockCopy(buffer, offset, this.ChunkBuffer, this.ChunkOffset, count);
                this._position += count;
                this.ChunkOffset += count;
            }
        }

        public override void Close()
        {
            this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "WCS: Closing: {0}, {1}, {2}, {3}, {4}", "-", "-", this.ChunkOffset, this.ChunkIndex, this.Position);

            base.Close();
            if (this.ChunkOffset > 0)
            {
                ProcessChunkAction(this.ChunkBuffer, 0, this.ChunkOffset, this.ChunkIndex);
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (disposing)
            {
                this.BufferManager.ReturnBuffer(this.ChunkBuffer);
            }
        }
    }
}